/*
 * Copyright 2011 Drew Wilder-Goodwin, Steve Feng
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "resources.h"
#include "cmdline.h"
#include "dxdiagcom.h"
#include "wine/debug.h"
#include "wine/unicode.h"
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <winuser.h>
#include <wchar.h>


WINE_DEFAULT_DEBUG_CHANNEL(dxdiag);

static BOOL PrepareFilename(WCHAR* filename, const enum OUTFORMAT format);
static void OutputPlainFile(FILE * hFile, DxDiag_Root g_dxdiagcom_root);
static void OutputSectionHeading(int section, FILE * hFile);
static void OutputNewline(FILE * hFile);
static void OutputWString(FILE * hFile, WCHAR * buffer);
static void OutputPlainPropertyTitle(int property, FILE * hFile);
static void OutputPlainProperty(int property, WCHAR * value, FILE * hFile);

/*
 Output dxdiag diagnostic information to outfile
 two output formats are supported:
 PLAIN (-t, /t, etc.) and
 XML (-x, /x, etc.)
 Returns FALSE if filename invalid or unable to be opened for writing
 */

BOOL OutputToFile(WCHAR * filename, const enum OUTFORMAT format, DxDiag_Root g_dxdiagcom_root)
{
    FILE *hFile;
    CHAR utf8Filename[MAX_STRING_LEN];
    char utf_openparams[] = "wb+";

    if (PrepareFilename(filename, format) == FALSE)
        return FALSE;

    WideCharToMultiByte(CP_UTF8, 0, filename, -1, utf8Filename,
                        MAX_STRING_LEN, NULL, NULL);

    if ((hFile = fopen(utf8Filename, utf_openparams)) == NULL) {
        WCHAR messageText[MAX_STRING_LEN];
        WCHAR messageTitle[MAX_STRING_LEN];
        LoadStringW(NULL, STRING_ERROR_OPENING_FILE, messageText,
                    MAX_STRING_LEN);
        LoadStringW(NULL, STRING_COMMON_ERROR, messageTitle,
                    MAX_STRING_LEN);
        MessageBoxW(NULL, messageText, messageTitle, MB_ICONEXCLAMATION
                    | MB_OK);
        return FALSE;
    }

    if (format == PLAIN)
        OutputPlainFile(hFile, g_dxdiagcom_root);
    else if (format == XML)
        WINE_FIXME("/x unimplemented\n");

    return TRUE;
}


/*
 Prepare a given filename for output:
  1. Set to default if empty
  2. Remove any trailing periods
  3. Reject any filenames with invalid characters
  4. Add an extension if not present
 Precondition: filename is a null terminated string with at least enough
               room to store itself and one 4 extra characters (extension)
 Postcondition: the above rules have been applied to filename if valid
 Return: TRUE if the conditions were applied successfully
         FALSE if the conditions could not be applied because filename invalid
 */

static BOOL PrepareFilename(WCHAR* filename, const enum OUTFORMAT format)
{
    static WCHAR s_default_filename[] = {'d','x','d','i','a','g',0};
    static WCHAR s_nonallowed_chars[] = {'<','>',':','"','/','\\','|','?','*',0};
    static WCHAR s_ext_plain[] = {'.','t','x','t',0};
    static WCHAR s_ext_xml[] = {'.','x','m','l',0};
    DWORD filename_len;
    WCHAR *checkchar, *badchar;

    filename_len = strlenW(filename);

    if (filename_len == 0) {
        lstrcpyW(filename, s_default_filename);
        filename_len = strlenW(filename);
    }

    /* trailing periods are not allowed in windows filenames */
    while (filename[filename_len-1] == '.') {
        filename[filename_len-1] = 0;
        filename_len -= 1;
        if (filename_len == 0) {
            /* input string must have been all periods */
            return FALSE;
        }
    }

    /* some characters not allowed in windows filenames */
    for (checkchar = filename; *checkchar != 0; checkchar++) {
        /* would like to use wcschr() here, but wine's headers use wchar_t
            there so it cannot easily be used */
        for (badchar = s_nonallowed_chars; *badchar != 0; badchar++) {
            if (*checkchar == *badchar) {
                return FALSE;
            }
        }
    }

    /* CHECKPOINT: based on manipulation above:
     *   1. there is at least one non-period character
     *   2. there are no disallowed characters
     *   3. the filename does not end in a period
    */

    /* If there is an extension (i.e. at least one period), were done. */
    for (checkchar = filename; *checkchar != 0; checkchar++) {
        if (*checkchar == '.') {
            /* assumes all other validty checks already done */
            return TRUE;
        }
    }

    /* no period was found, extension will be appended */
    /* FIXME: handle size of filename buffer better */
    if (format == PLAIN) {
        lstrcatW(filename, s_ext_plain);
    } else if (format == XML) {
        lstrcatW(filename, s_ext_xml);
    } else {
        /* unknown format */
        return FALSE;
    }

    return TRUE;
}


static void OutputPlainFile(FILE * hFile, DxDiag_Root g_dxdiagcom_root)
{
    DxDiag_SystemInfo *systemInfo = g_dxdiagcom_root.systemInfo;
    OutputSectionHeading(TXT_SYSTEM, hFile);
    OutputPlainProperty(TXT_SYSTEM_TIME, systemInfo->szTimeEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_MACHINENAME,
                        systemInfo->szMachineNameLocalized, hFile);
    OutputPlainProperty(TXT_SYSTEM_OS, systemInfo->szOSExLongEnglish,
                        hFile);
    OutputPlainProperty(TXT_SYSTEM_LANGUAGE,
                        systemInfo->szLanguagesEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_MANUFACTURER,
                        systemInfo->szSystemManufacturerEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_MODEL,
                        systemInfo->szSystemModelEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_BIOS, systemInfo->szBIOSEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_PROCESSOR,
                        systemInfo->szProcessorEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_MEMORY,
                        systemInfo->szPhysicalMemoryEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_PAGEFILE,
                        systemInfo->szPageFileLocalized, hFile);
    OutputPlainProperty(TXT_SYSTEM_WINDIR, systemInfo->szWindowsDir,
                        hFile);
    OutputPlainProperty(TXT_SYSTEM_DXVERSION,
                        systemInfo->szDirectXVersionLongEnglish, hFile);
    OutputPlainProperty(TXT_SYSTEM_DXSETUPPARAMS,
                        systemInfo->szSetupParamEnglish, hFile);
}


/*
 Output a section heading to the file pointed to by hFile in a format:
 ------------
 Section Name
 ------------
 Precondition: existing string table entry for int section
               hFile is a valid file handle to a writable file
 */

static void OutputSectionHeading(int section, FILE * hFile)
{
    WCHAR text[MAX_STRING_LEN];
    WCHAR separator[MAX_STRING_LEN];
    DWORD len, i;

    LoadStringW(NULL, section, text, MAX_STRING_LEN);

    i = 0;
    len = lstrlenW(text);
    while (len-- > 0)
        separator[i++] = '-';
    separator[i] = '\0';

    OutputNewline(hFile);
    OutputWString(hFile, separator);
    OutputNewline(hFile);
    OutputWString(hFile, text);
    OutputNewline(hFile);
    OutputWString(hFile, separator);
    OutputNewline(hFile);
}

static void OutputPlainPropertyTitle(int property, FILE * hFile)
{
    static WCHAR s_colonSpace[] = { ':', ' ', 0 };
    WCHAR text[MAX_STRING_LEN];

    LoadStringW(NULL, property, text, MAX_STRING_LEN);

    OutputWString(hFile, text);
    OutputWString(hFile, s_colonSpace);
}

static void OutputPlainProperty(int property, WCHAR * value, FILE * hFile)
{
    OutputPlainPropertyTitle(property, hFile);
    OutputWString(hFile, value);
    OutputNewline(hFile);
}

static void OutputNewline(FILE * hFile)
{
    fputc('\r', hFile);
    fputc('\n', hFile);
}

static void OutputWString(FILE * hFile, WCHAR * buffer)
{
    DWORD nNumBytesToWrite;
    DWORD utf8BufferSize;
    CHAR *utf8Buffer;

    if ((!buffer) || (*buffer == '\0'))
        return;

    utf8BufferSize = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
    utf8Buffer = HeapAlloc(GetProcessHeap(), 0, utf8BufferSize);
    if (utf8Buffer == NULL)
        return;

    nNumBytesToWrite = WideCharToMultiByte(CP_UTF8, 0, buffer, -1,
                                                utf8Buffer, utf8BufferSize, NULL, NULL);

    if (nNumBytesToWrite != 0)
        fprintf(hFile, "%s", utf8Buffer);

    HeapFree(GetProcessHeap(), 0, utf8Buffer);

}
